<template>
  <g class="segmentation">
    <Selection :stageWidth="stageWidth" :stageHeight="stageHeight" :state="state" :handlePointClick="handlePointClick"
      :handleChange="handleChange" :draw="draw" :transformZoom="transformZoom" />
      <PolygonRender
        v-for="shape in state.shapes"
        :key="shape.id"
        :shape="shape"
        :stageWidth="stageWidth"
        :stageHeight="stageHeight"
        :draw="draw"
        :transformer="transformer"
        :guides="state.guides"
        :scale="scale"
        :getZoom="getZoom"
        :bounds="bounds"
        :offset="offset"
        :handleChange="handleChange"
        :currentAnnotationId="currentAnnotationId"
        :onDragStart="onDragStart"
        :onDragMove="onDragMove"
        :onDragEnd="onDragEnd"
        :setTransformer="setTransformer"
    />
  </g>
</template>

<script setup>
import { reactive, watch, defineExpose  } from 'vue';
import Selection from './selection'
import { useDraw } from '@/hooks';
import { raise, replace, getPolygonExtent } from '@/utils';
import update from 'immutability-helper';
import { last, isFunction, isEmpty } from 'lodash';
import PolygonRender from './polygon';
const stageWidth = 800;
const stageHeight = 600;
const props = defineProps({
  stageWidth: Number,
  stageHeight: Number,
  className: String,
  shapes: Array,
  updateState: Function,
  setCurAnnotation: Function,
  currentAnnotationId: String,
  transformZoom: Function,
  getZoom: Function,
  scale: Number,
  bounds: Object,
  offset: Function,
})
const emits = defineEmits(["change"]);
const state = reactive({
  // 已经完成的形状
  shapes: props.shapes || [],
  // 当前正在绘制的形状
  unfinishedShape: { points: [] },
  // 引导线
  guides: [],
  // 当前操作
  status: ''
});
// 标注拖拽偏移
const transformer = reactive({
  id: undefined,
  dx: 0,
  dy: 0,
  x: undefined,
  y: undefined,
});
const { updateState, setCurAnnotation, getZoom } = props;
const { draw, onDrawStart, onDrawMove, onDrawEnd } = useDraw();

// 上传标注校验格式校验
const validate = () => {
  if (!isEmpty(state.unfinishedShape)) {
    return false;
  }
  return true;
};

const setState = (key, params, callback) => {
  // 区分函数式更新和对象更新
  if (typeof params === 'function') {
    const next = params(state);
    Object.assign(state, { [key]: next });
    if (isFunction(callback)) {
      callback(state);
    }
    return;
  }
  // 普通更新
  Object.assign(state, { [key]: params });
  if (isFunction(callback)) {
    callback(state);
  }
};
// 更新标注偏移
const setTransformer = (params) => {
  Object.assign(transformer, params);
};

const syncShapes = (type, options = {}) => (nextState) => {
  // 数据同步
  // state: 数据
  // options: event 等事件参数
  fireChange({ type, state: nextState, options });
};
// 根据缩放比例生成相对偏移
const getValidDrag = (drag, shape, options = { bounds: true }) => {
  // 兼容图片缩放比例
  const scale = options.scale || props.scale;
  const { zoom } = getZoom();
  const _scale = zoom * scale;

  const { x0, x1, y0, y1 } = getPolygonExtent(shape);
  if (!options.bounds) {
    return {
      ...drag,
      validDx: drag.dx / _scale,
      validDy: drag.dy / _scale,
    };
  }
  const validDx =
    drag.dx > 0
      ? Math.min(drag.dx / _scale, props.bounds.width - x1)
      : Math.max(drag.dx / _scale, -x0);

  const validDy =
    drag.dy > 0
      ? Math.min(drag.dy / _scale, props.bounds.height - y1)
      : Math.max(drag.dy / _scale, -y0);
  return {
    ...drag,
    validDx,
    validDy,
  };
};
// 每次拖拽的优先级提升
const onDragStart = (draw, shape) => {
  const index = state.shapes.findIndex((d) => d.id === shape.id);
  if (index > -1) {
    const raised = raise(state.shapes, index);
    Object.assign(state, {
      shapes: raised,
    });
  }
  setState('status', 'EDITING');
  // 同步当前标注
  setCurAnnotation(shape);
  fireChange({ type: 'DRAG_START', state });
};

// 根据 drag 自主生成 transformer
const onDragMove = ({ drag }, shape) => {
  const { validDx, validDy } = getValidDrag(drag, shape.data.points, { scale: 1 });
  setTransformer({
    isDragging: true,
    id: shape.id,
    x: drag.x,
    y: drag.y,
    dx: validDx,
    dy: validDy,
  });
  fireChange({ type: 'DRAG_MOVE', state });
};

const onDragEnd = ({ drag }, shape) => {
  updateState(
    (prev) => {
      const index = prev.shapes.findIndex((d) => d.id === shape.id);
      if (index > -1) {
        const selectedItem = prev.shapes[index];
        const _nextItem = {
          ...selectedItem,
          data: {
            ...selectedItem.data,
            points: selectedItem.data.points.map((point) => ({
              x: point.x + drag.validDx,
              y: point.y + drag.validDy,
            })),
          },
        };

        const next = replace(prev.shapes, index, _nextItem);
        return {
          ...prev,
          shapes: next,
        };
      }
      return prev;
    },
    (nextState) => fireChange({ type: 'DRAG_END', state: nextState })
  );
};

// 绘制点
const drawPoint = (point, callback) => {
  const { points = [] } = state.unfinishedShape;
  const nextPoints = update(points, { $push: [point] });

  setState(
    'unfinishedShape',
    (state) =>
      update(state.unfinishedShape, {
        points: {
          $set: nextPoints,
        },
      }),
    callback
  );
};
// 重置标注选中
  const setCurrentAnnotation = (annotationId) => {
    updateState({
      currentAnnotationId: annotationId || '',
    });
  };

// 根据异步结果更新状态
const handleChange = (eventType, params = {}) => {
  switch (eventType) {
    // 绘制点
    case 'DRAW_START': {
      const { point } = params;
      onDrawStart(point);
      // 绘图
      setState('status', 'DRAWING');
      drawPoint(point, () => setCurrentAnnotation());
      break;
    }
    // 绘制节点
    case 'DRAW_POINT': {
      const { point } = params;
      drawPoint(point);
      break;
    }
    case 'DRAW_MOVE': {
      const { point } = params;
      const { points = [] } = state.unfinishedShape;
      draw.start && onDrawMove(point);

      // 绘制折线图才需要去触发 guideline
      if (points.length) {
        const nextGuides = [last(points), point];
        setState('guides', nextGuides);
      }

      break;
    }
    // 绘制线借结束
    case 'DRAW_END': {
      const { unfinishedShape } = state;
      // 绘制结束
      onDrawEnd();
      // 根据最后一条shape 记录决定更新类型
      const lastShape = last(state.shapes);
      // 如果不存在数据，直接写入，否则 push
      const ACTION = !lastShape?.id ? '$set' : '$push';
      // 完成状态
      setState('status', 'FINISHED');
      setState(
        'shapes',
        (state) =>
          update(state.shapes, {
            [ACTION]: [{ points: unfinishedShape.points }],
          }),
        syncShapes('DRAW_END', params)
      );

      reset();
      break;
    }
    // 新增节点
    case 'ADD_POINT': {
      const { index, point, shape } = params;
      setState(
        'shapes',
        (state) => {
          // 当前 shape 索引
          const shapeIndex = state.shapes.findIndex((d) => d.id === shape.id);
          const updateShape = update(state.shapes[shapeIndex], {
            data: {
              points: {
                $splice: [[index, 0, point]],
              },
            },
          });

          return update(state.shapes, {
            [shapeIndex]: {
              $set: updateShape,
            },
          });
        },
        syncShapes('ADD_POINT')
      );
      break;
    }
    // 移动节点
    case 'MOVE_POINT': {
      // shapes 没有更新
      const guides = [];
      if (params) {
        const { drag, index, shape } = params;
        const { length } = shape.data.points;

        const { validDx, validDy } = getValidDrag(drag, shape.data.points, {
          bounds: false,
        });

        const prevPoint = shape.data.points[index];
        const point = {
          x: prevPoint.x + validDx,
          y: prevPoint.y + validDy,
        };

        guides.push(
          [point, shape.data.points[(index + 1) % length]],
          [point, shape.data.points[(index - 1 + length) % length]]
        );
      }
      setState('guides', guides, syncShapes('MOVE_POINT'));
      break;
    }
    // 更新节点位置
    case 'UPDATE_POINT': {
      const { index, shape } = params;

      setState(
        'shapes',
        (state) => {
          // 当前 shape 索引
          // hack: 双击会触发更新节点
          if (!state.guides.length) return state.shapes;
          const shapeIndex = state.shapes.findIndex((d) => d.id === shape.id);
          const updateShape = update(state.shapes[shapeIndex], {
            data: {
              points: {
                $splice: [[index, 1, state.guides[0][0]]],
              },
            },
          });
          return update(state.shapes, {
            [shapeIndex]: {
              $set: updateShape,
            },
          });
        },
        syncShapes('UPDATE_POINT')
      );
      setState('guides', []);
      break;
    }
    case 'REMOVE_POINT': {
      const { index, shape } = params;
      //  不足 4个点不做处理
      if (shape.data.points.length <= 3) return;
      setState(
        'shapes',
        (state) => {
          // 当前 shape 索引
          const shapeIndex = state.shapes.findIndex((d) => d.id === shape.id);
          const updateShape = update(state.shapes[shapeIndex], {
            data: {
              points: {
                $splice: [[index, 1]],
              },
            },
          });

          return update(state.shapes, {
            [shapeIndex]: {
              $set: updateShape,
            },
          });
        },
        syncShapes('REMOVE_POINT')
      );
      break;
    }
    default:
      throw new Error('unknown eventType: ', eventType);
  }
};
// 结束调用
const handlePointClick = (index, shape, event) => {
  event.stopPropagation();
  if (index === 0) {
    finishDraw(event);
  }
  return false;
};


// 完成绘制
const finishDraw = (event) => {
  if (state.unfinishedShape.points.length >= 3) {
    const params = {
      event,
      shape: state.unfinishedShape,
    };
    handleChange('DRAW_END', params);
  }
};
const fireChange = (params) => {
  emits('change', params);
};
const removeData = (shapes) => {
  updateState({
    shapes: shapes
  });
  reset();
}
defineExpose({
  removeData
})
const reset = () => {
  Object.assign(state, {
    guides: [],
    unfinishedShape: {},
    status: '',
  });
  // 触发绘制结束
  !!draw.isDrawing && onDrawEnd();
};
watch(
  () => props.shapes,
  (next) => {
    state.shapes = next;
  }
);
</script>
<style lang="less"></style>